/*
 *
 * Pascal Virtual Machine Class is defined below.
 *
 */

package compiler;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;

class CPascalVirtualMachine implements PVM {
	private int[] memory; //Memory of Virtual Machine.
	private int register_Prgrm; //Register for Program code.
	private int register_Block; //Register for Active block.
	private int register_Stack; //Register for Stack top.
	private String fileName; //The file name of program to execute.
	private BufferedReader objFile; //The file pointer to the file to execute.
	private int stackBottom; //The pointer to Stack bottom.
	private boolean running; //The flag of Running.

	public CPascalVirtualMachine() {
		memory = new int[MEM_MAX];
		fileName = new String();
	}

	String readLine() {

		String str = null;
		try {
			str =
				new BufferedReader(new InputStreamReader(System.in)).readLine();
		} catch (Exception ex) {
			System.err.println("Failed to readline from System.in!");
		}

		return str;
	}

	public void run(String name) throws IOException {
		File input;
		if (name.length() == 0) {
			System.out.println("Input work directory please:");
			int ch;
			File dir;

			String dirStr = readLine();
		
			if (dirStr.length() != 0) {
				dir = new File(dirStr);
			} else {
				dir = new File("d:\\pascal");
			}
			System.out.println("Input the Runable file(*.EXP) please:");
			StringBuffer fileBuf = new StringBuffer();
			while ((ch = System.in.read()) != '\n')
				fileBuf.append(ch);
			if (fileBuf.length() != 0) {
				String file = fileBuf.toString();
				if (!(file.endsWith(".exp")))
					file.concat(".exp");
				input = new File(dir, file);
			} else {
				input = new File(dir, "default.exp");
			}
			fileName = dirStr + fileBuf.toString();
		} else {
			input = new File(name);
			fileName = name;
		}
		if (input.isFile() && input.canRead()) {
			objFile = new BufferedReader(new FileReader(input));
			LoadProgram();
			objFile.close();
			RunProgram();
		} else {
			System.out.println("Can't read file " + fileName + '.');
		}
	}

	void Reset() throws FileNotFoundException {
		for (int i = MEM_MIN; i < MEM_MAX; memory[i++] = 0);
		register_Prgrm = MEM_MIN;
		register_Block = MEM_MAX;
		register_Stack = MEM_MAX;
		//fileName = "d:\\pascal\\default.EXP";
		//objFile = new DataInputStream(new FileInputStream(new File(fileName)));;
		stackBottom = MEM_MAX;
		running = false;
	}

	private void Error(int lineNo, String text) {
		System.out.print("Line: " + lineNo + '.');
		System.out.print("Stack register SP=" + register_Stack + '.');
		System.out.println(text);
		running = false;
	}

	//? Put variable address to the top of the stack
	//  Stack state:
	//  Action:
	//         [++sp] <= &x   // x: Variable;
	private void Variable(int level, int displacement) {
		int x = register_Block;
		register_Stack++;
		while (level > 0) {
			x = memory[x];
			level--;
		}
		memory[register_Stack] = x + displacement;
		register_Prgrm += 3;
	}

	//? Put 4Byte variable value to the top of the stack
	//  Stack state:
	//  Action:
	//         [++sp] <= x   // x: 4Byte Variable;
	private void VarParam(int level, int displacement) {
		int x = register_Block;
		register_Stack++;
		while (level > 0) {
			x = memory[x];
			level--;
		}
		memory[register_Stack] = memory[x + displacement];
		register_Prgrm += 3;
	}

	//? Get the address of an array member
	//  lower, upper: The low & up limit of the Array
	//  len: the size of the member
	//  Stack state:
	//        &A = [sp - 1] //A: Array variable; i: Integer value
	//        i = [sp]
	//  Action:
	//        [--sp] <= &(A[x])
	private void Index(int lower, int upper, int len, int lineNo) {
		int i = memory[register_Stack];
		register_Stack--;
		if ((i < lower) || (i > upper))
			Error(lineNo, "Range Error.");
		else
			memory[register_Stack] += (i - lower) * len;
		register_Prgrm += 5;
	}

	//? Get the address of an record member
	//  displ: The member's displacement of the record
	//  Stack state:
	//        &R = [sp] // R: Record variable; x: Record member
	//  Action:
	//        [sp] <= &(R.x)
	private void Field(int displacement) {
		memory[register_Stack] += displacement;
		register_Prgrm += 2;
	}

	//{Factor="Constant"|Variable Access "Value"|Expression|Factor"Not"}
	//? Put the 4Byte constant to the top of the Stack
	//  value: The constant value
	//  Stack state:
	//  Action:
	//        [++sp] <= c // c: 4Byte Constant
	private void Constant(int value) {
		register_Stack++;
		memory[register_Stack] = value;
		register_Prgrm += 2;
	}

	//? Put the variable value to the top of the Stack
	//  len: The size of the variable
	//  Stack state:
	//         &x = [sp] // x: Variable
	//  Action:
	//        [sp]..[sp + sizeof x - 1] <= x
	//        sp <= sp + sizeof x - 1
	private void Value(int len) {
		int x = memory[register_Stack];
		int i = 0;
		for (i = 0;
			i <= len - 1;
			memory[register_Stack + i] = memory[x + i], i++);
		register_Stack += len - 1;
		register_Prgrm += 2;
	}

	//? NOT
	//  Stack state:
	//        x = [sp]  // x: Boolean
	//  Action:
	//        [sp] <= NOT x
	private void NotX() {
		if (memory[register_Stack] == 0)
			memory[register_Stack] = 1;
		else
			memory[register_Stack] = 0;
		register_Prgrm++;
	}

	//{Term = Factor | Factor Multiplying Operator}
	//{Multiplying Operator = "Multiply" | "Divide" | "Modulo" | "And"}
	//? Mul
	//  Stack state:
	//        x = [sp - 1] // x, y: Integer value
	//        y = [sp]
	//  Action:
	//        [--sp] <= x * y
	private void Multiply() {
		register_Stack--;
		memory[register_Stack] *= memory[register_Stack + 1];
		register_Prgrm++;
	}

	//? Div
	//  Stack state:
	//        x = [sp - 1] // x, y: Integer value
	//        y = [sp]
	//  Action:
	//        [--sp] <= x \ y
	private void Divide() {
		register_Stack--;
		memory[register_Stack] /= memory[register_Stack + 1];
		register_Prgrm++;
	}

	//? mod
	//  Stack state:
	//        x = [sp - 1] // x, y: Integer value
	//        y = [sp]
	//  Action:
	//        [--sp] <= x % y
	private void Modulo() {
		register_Stack--;
		memory[register_Stack] %= memory[register_Stack + 1];
		register_Prgrm++;
	}

	//? And
	//  Stack state:
	//        x = [sp - 1] // x, y: Boolean value
	//        y = [sp]
	//  Action:
	//        [--sp] <= x && y
	private void AndX() {
		register_Stack--;
		if (memory[register_Stack] != 0)
			memory[register_Stack] = memory[register_Stack + 1];
		register_Prgrm++;
	}

	//{ SimpleExoression = Term [SignOPerator] | Term Adding OPerator }
	//{ SignOperator = Empty | "Minus" }
	//{ AddingOPerator = "ADD" | "SUBTRACT" | "OR" }
	//? Neg
	//  Stack state:
	//        x = [sp] // x: Integer value
	//  Action:
	//        [sp] <= -x
	private void Minus() {
		memory[register_Stack] = -memory[register_Stack];
		register_Prgrm++;
	}

	//? Add
	//  Stack state:
	//        x = [sp - 1] // x, y: Integer value
	//        y = [sp]
	//  Action:
	//        [--sp] <= x + y
	private void Add() {
		register_Stack--;
		memory[register_Stack] += memory[register_Stack + 1];
		register_Prgrm++;
	}

	//? Sub
	//  Stack state:
	//        x = [sp - 1] // x, y: Integer value
	//        y = [sp]
	//  Action:
	//        [--sp] <= x - y
	private void Subtract() {
		register_Stack--;
		memory[register_Stack] -= memory[register_Stack + 1];
		register_Prgrm++;
	}

	//? Or
	//  Stack state:
	//        x = [sp - 1] // x, y: Boolean value
	//        y = [sp]
	//  Action:
	//        [--sp] <= x || y
	private void OrX() {
		register_Stack--;
		if (memory[register_Stack] == 0)
			memory[register_Stack] = memory[register_Stack + 1];
		register_Prgrm++;
	}

	//{ Expression = SimpleExpression[SompleExpression RelationalOperator] }
	//{ RelationOPerator = "Less" | "Equal" | "Greater" | "NotGreater" | "NotEqual" | "NotLess"}
	//? Less
	//  Stack state:
	//        x = [sp - 1] // x, y: Integer value
	//        y = [sp]
	//  Action:
	//        [--sp] <= (x < y)
	private void Less() {
		register_Stack--;
		if (memory[register_Stack] < memory[register_Stack + 1])
			memory[register_Stack] = 1;
		else
			memory[register_Stack] = 0;
		register_Prgrm++;
	}

	//? Equ
	//  Stack state:
	//        x = [sp - 1] // x, y: Integer value
	//        y = [sp]
	//  Action:
	//        [--sp] <= (x == y)
	private void Equal() {
		register_Stack--;
		if (memory[register_Stack] == memory[register_Stack + 1])
			memory[register_Stack] = 1;
		else
			memory[register_Stack] = 0;
		register_Prgrm++;
	}

	//? Greater
	//  Stack state:
	//        x = [sp - 1] // x, y: Integer value
	//        y = [sp]
	//  Action:
	//        [--sp] <= (x > y)
	private void Greater() {
		register_Stack--;
		if (memory[register_Stack] > memory[register_Stack + 1])
			memory[register_Stack] = 1;
		else
			memory[register_Stack] = 0;
		register_Prgrm++;
	}

	//? NotGreater
	//  Stack state:
	//        x = [sp - 1] // x, y: Integer value
	//        y = [sp]
	//  Action:
	//        [--sp] <= (x <= y)
	private void NotGreater() {
		register_Stack--;
		if (memory[register_Stack] <= memory[register_Stack + 1])
			memory[register_Stack] = 1;
		else
			memory[register_Stack] = 0;
		register_Prgrm++;
	}

	//? NotEqu
	//  Stack state:
	//        x = [sp - 1] // x, y: Integer value
	//        y = [sp]
	//  Action:
	//        [--sp] <= (x != y)
	private void NotEqual() {
		register_Stack--;
		if (memory[register_Stack] != memory[register_Stack + 1])
			memory[register_Stack] = 1;
		else
			memory[register_Stack] = 0;
		register_Prgrm++;
	}

	//? NotLess
	//  Stack state:
	//        x = [sp - 1] // x, y: Integer value
	//        y = [sp]
	//  Action:
	//        [--sp] <= (x >= y)
	private void NotLess() {
		register_Stack--;
		if (memory[register_Stack] >= memory[register_Stack + 1])
			memory[register_Stack] = 1;
		else
			memory[register_Stack] = 0;
		register_Prgrm++;
	}

	//? Read an Integer Variable
	//  Stack state:
	//        &x = [sp] // x: Integer Variable
	//  Action:
	//        Read(x)
	//        sp--
	private void ReadX() throws IOException {
		byte buf[] = new byte[256];
		int length;
		String str;
		int value = 0;
		while (true) {
			try {
				value = Integer.parseInt(readLine());
				break;
			} catch (NumberFormatException ex) {
				System.out.println("Illegal integer number!");
			}
		}
		memory[memory[register_Stack]] = value;
		register_Stack--;
		register_Prgrm++;
	}

	//? Writeln an Integer Value
	//  Stack state:
	//        x = [sp] // x: Integer Value
	//  Action:
	//        Writeln(x)
	//        sp--
	private void WriteIntX() {
		System.out.println(memory[register_Stack]);
		register_Stack--;
		register_Prgrm++;
	}

	//? Writeln a Boolean Value
	//  Stack state:
	//        x = [sp] // x: Boolean Value
	//  Action:
	//        Writeln(x)
	//        sp--
	private void WriteBooleanX() {
		if (memory[register_Stack] != 0)
			System.out.println("TRUE");
		else
			System.out.println("FALSE");
		register_Stack--;
		register_Prgrm++;
	}

	//? Call a Procedure
	//        Make Active Block
	private void ProcCall(int level, int displacement) {
		register_Stack++;
		int x = register_Block;
		while (level > 0) {
			x = memory[x];
			level--;
		}
		memory[register_Stack] = x;
		memory[register_Stack + 1] = register_Block;
		memory[register_Stack + 2] = register_Prgrm + 3;
		register_Block = register_Stack;
		register_Stack = register_Block + 2;
		register_Prgrm += displacement;
	}

	//{ AssignmentStatement = ValiableAccess Expression "Assign" }
	private void Assign(int len) {
		register_Stack -= len - 1;
		int x = memory[register_Stack + 1];
		int y = register_Stack + 2;
		int i = 0;
		while (i < len) {
			memory[x + i] = memory[y + i];
			i++;
		}
		register_Prgrm += 2;
	}

	private void DoX(int displacement) {
		if (memory[register_Stack] == 1)
			register_Prgrm += 2;
		else
			register_Prgrm += displacement;
		register_Stack -= 1;
	}

	private void GotoX(int displacement) {
		register_Prgrm += displacement;
	}

	private void ProcedureX(
		int varlen,
		int tempLen,
		int displacement,
		int lineNo) {
		register_Stack += varlen;
		if (register_Stack + tempLen > MEM_MAX)
			Error(lineNo, "Stack Limit");
		else
			register_Prgrm += displacement;
	}

	private void EndProc(int paramLen) {
		register_Stack = register_Block - paramLen - 1;
		register_Prgrm = memory[register_Block + 2];
		register_Block = memory[register_Block + 1];
	}

	//{Program = "Pragram"Blockbody"} Program"}
	private void ProgramX(
		int varlen,
		int tempLen,
		int displacement,
		int lineNo) {
		register_Block = stackBottom;
		register_Stack = register_Block + 2 + varlen;
		if (register_Stack + tempLen > MEM_MAX)
			Error(lineNo, "Stack Limit");
		else
			register_Prgrm += displacement;
	}

	private void EndProg() {
		running = false;
	}

	//{LocalVal(displ) = Variable(0,displ)}
	private void LocalVar(int displacement) {
		register_Stack++;
		memory[register_Stack] = register_Block + displacement;
		register_Prgrm += 2;
	}

	//{LocalValue(displ) = LocalVar(displ) Value(1)}
	private void LocalValue(int displacement) {
		register_Stack++;
		memory[register_Stack] = memory[register_Block + displacement];
		register_Prgrm += 2;
	}

	//{GlobalValue(displ) = Variable(1,displ)}
	private void GlobalValue(int displacement) {
		register_Stack++;
		memory[register_Stack] = memory[register_Block] + displacement;
		register_Prgrm += 2;
	}

	//{GobalVal(displ) = GobalVAR(displ) Value(1)}
	private void GlobalVar(int displacement) {
		register_Stack++;
		memory[register_Stack] = memory[memory[register_Block] + displacement];
		register_Prgrm += 2;
	}

	//{simpleValue = Value(1)}
	private void SimpleValue() {
		memory[register_Stack] = memory[memory[register_Stack]];
		register_Prgrm++;
	}

	//{simpleAssign = Assign(1)}
	private void SimpleAssign() {
		memory[memory[register_Stack - 1]] = memory[register_Stack];
		register_Stack -= 2;
		register_Prgrm++;
	}

	//{GobalCall(displ) = ProcCall(1, displ)}
	private void GlobalCall(int displacement) {
		memory[register_Stack + 1] = memory[register_Block];
		memory[register_Stack + 2] = register_Block;
		memory[register_Stack + 3] = register_Prgrm + 2;
		register_Block = register_Stack + 1;
		register_Stack = register_Block + 2;
		register_Prgrm += displacement;
	}

	private void MyVarPram(int displacement) {
		int x = register_Block;
		int level = memory[memory[x] + displacement];
		displacement = memory[memory[x] + displacement + 1];
		register_Stack++;
		while (level > 0) {
			x = memory[x];
			level--;
		}
		memory[register_Stack] = memory[x] + displacement;
		register_Prgrm += 2;
	}

	private void RunProgram() throws IOException {
		running = true;
		register_Prgrm = MEM_MIN;
		int op;
		while (running) {
			//int op= memory[register_Prgrm];
			op = memory[register_Prgrm];
			switch (op) {
				case OP_ADD :
					Add();
					break;

				case OP_AND :
					AndX();
					break;

				case OP_ASSIGN :
					Assign(memory[register_Prgrm + 1]);
					break;

				case OP_CONST :
					Constant(memory[register_Prgrm + 1]);
					break;

				case OP_DIV :
					Divide();
					break;

				case OP_DO :
					DoX(memory[register_Prgrm + 1]);
					break;

				case OP_EPROC :

					EndProc(memory[register_Prgrm + 1]);
					break;

				case OP_EPROG :
					EndProg();
					break;

				case OP_EQU :
					Equal();
					break;

				case OP_FIELD :
					Field(memory[register_Prgrm + 1]);
					break;

				case OP_GT :
					Greater();
					break;

				case OP_GOTO :
					GotoX(memory[register_Prgrm + 1]);
					break;

				case OP_INDEX :
					Index(
						memory[register_Prgrm + 1],
						memory[register_Prgrm + 2],
						memory[register_Prgrm + 3],
						memory[register_Prgrm + 4]);
					break;

				case OP_LT :
					Less();
					break;

				case OP_MINUS :
					Minus();
					break;

				case OP_MOD :
					Modulo();
					break;

				case OP_MUL :
					Multiply();
					break;

				case OP_NE :
					NotEqual();
					break;

				case OP_NGT :
					NotGreater();
					break;

				case OP_NLT :
					NotLess();
					break;

				case OP_NOT :
					NotX();
					break;

				case OP_OR :
					OrX();
					break;

				case OP_CALL :
					ProcCall(
						memory[register_Prgrm + 1],
						memory[register_Prgrm + 2]);
					break;

				case OP_PROC :
					ProcedureX(
						memory[register_Prgrm + 1],
						memory[register_Prgrm + 2],
						memory[register_Prgrm + 3],
						memory[register_Prgrm + 4]);
					break;

				case OP_PROG :
					ProgramX(
						memory[register_Prgrm + 1],
						memory[register_Prgrm + 2],
						memory[register_Prgrm + 3],
						memory[register_Prgrm + 4]);
					break;

				case OP_READ :
					ReadX();
					break;

				case OP_SUB :
					Subtract();
					break;

				case OP_VALUE :
					Value(memory[register_Prgrm + 1]);
					break;

				case OP_VARIABLE :
					Variable(
						memory[register_Prgrm + 1],
						memory[register_Prgrm + 2]);
					break;

				case OP_VARPRM :
					VarParam(
						memory[register_Prgrm + 1],
						memory[register_Prgrm + 2]);
					break;

				case OP_WRITEINT :
					WriteIntX();
					break;

				case OP_WRITEBOOL :
					WriteBooleanX();
					break;

				case OP_LOCVALUE :
					LocalValue(memory[register_Prgrm + 1]);
					break;

				case OP_LOCVAR :
					LocalVar(memory[register_Prgrm + 1]);
					break;

				case OP_GLBVALUE :
					GlobalValue(memory[register_Prgrm + 1]);
					break;

				case OP_GLBVAR :
					GlobalVar(memory[register_Prgrm + 1]);
					break;

				case OP_SMPVAL :
					SimpleValue();
					break;

				case OP_SMPASSIGN :
					SimpleAssign();
					break;

				case OP_GLBCALL :
					GlobalCall(memory[register_Prgrm + 1]);
					break;
			}
		}
	}

	private int ChageToInt(String s) {
		int n = 0;
		char ch;
		boolean nflag = false;
		int i = 0;
		if (s.charAt(0) == '-') {
			nflag = true;
			i = 1;
		}
		for (; i < s.length(); i++) {
			ch = s.charAt(i);
			if (!Character.isDigit(ch))
				break;
			n = n * 10 + ch - '0';
		}
		return (nflag) ? -n : n;
	}

	private void LoadProgram() throws IOException {
		int x = MEM_MIN;
		int readInt;
		String line;
		while (true) {
			line = objFile.readLine();
			if (line == null)
				break;
			readInt = ChageToInt(line);
			memory[x] = readInt;
			x++;
		}
		stackBottom = x;
	}

}
